#include "Gk.h"

#ifdef GK_DEVICE_OPENGL

#pragma warning (disable:4100)	// disable unused parameter warning until everything is implemented

// #include "../../../sdk/glee/GLee.h"
// #include <gl/glu.h>

#include "../GkContext.GenericDeferred.h"
#include "GkDevice.OpenGL.h"
#include "GkTypeConversion.OpenGL.h"

#pragma comment( lib, "opengl32.lib" )

#define VALIDATE_GL(c)	{ c; GLenum res = glGetError(); CR_ASSERT_MSG1(res == GL_NO_ERROR, "OpenGL Error 0x%X", res); }

namespace gk
{
	DeviceHandle CreateDevice()
	{
		return new DeviceOpenGL;
	}

	void DeviceOpenGL::Initialise(InitParamsHandle hInitParams)
	{
		InitialisePlatform(hInitParams);

		static	PIXELFORMATDESCRIPTOR pfd =				// pfd Tells Windows How We Want Things To Be
		{
			sizeof(PIXELFORMATDESCRIPTOR),				// Size Of This Pixel Format Descriptor
			1,											// Version Number
			PFD_DRAW_TO_WINDOW |						// Format Must Support Window
			PFD_SUPPORT_OPENGL |						// Format Must Support OpenGL
			PFD_DOUBLEBUFFER,							// Must Support Double Buffering
			PFD_TYPE_RGBA,								// Request An RGBA Format
			32,											// Select Our Color Depth
			0, 0, 0, 0, 0, 0,							// Color Bits Ignored
			0,											// No Alpha Buffer
			0,											// Shift Bit Ignored
			0,											// No Accumulation Buffer
			0, 0, 0, 0,									// Accumulation Bits Ignored
			16,											// 16Bit Z-Buffer (Depth Buffer)  
			0,											// No Stencil Buffer
			0,											// No Auxiliary Buffer
			PFD_MAIN_PLANE,								// Main Drawing Layer
			0,											// Reserved
			0, 0, 0										// Layer Masks Ignored
		};

		GLuint pixelFormat;

		m_hDC = GetDC((HWND)pf::GetPlatform()->GetPlatformWindowHandle());
		pixelFormat = ChoosePixelFormat(m_hDC, &pfd);
		SetPixelFormat( m_hDC, pixelFormat, &pfd );	
		m_hRC = wglCreateContext( m_hDC );			
		wglMakeCurrent(m_hDC, m_hRC);

		// Check extensions
		CR_ASSERT(GLEE_ARB_vertex_buffer_object);
		CR_ASSERT(GLEE_EXT_framebuffer_object);

		// How many colour targets can we have in a framebuffer
		m_maxColourTargets = 0;
		glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, (GLint*)&m_maxColourTargets);
		m_maxColourTargets = cr::Maths::Max(m_maxColourTargets, Constants::MaxColourTargets);

		// Setup back-buffer
		GetBackBuffer()->m_internal.m_bufferId = 0;

		// Common initialisation
		DeviceCommon::InitialiseCommon(hInitParams);
 	}

	void DeviceOpenGL::Shutdown()
	{

	}

	bool DeviceOpenGL::Present()
	{
		SwapBuffers(m_hDC);
		return DeviceCommon::Present();
	}

	// Object creation
	void DeviceOpenGL::CreateContext(ContextHandle hContext)
	{
		switch (hContext->m_type)
		{
		case ContextType::Immediate:
			{
// 				ContextInternal newContext;
// 				hContext->m_dataBlock.Assign(newContext);
				break;
			}

		case ContextType::Deferred:
			{
// 				ContextGenericDeferred newContext;
// 				hContext->m_dataBlock.Assign(newContext);
				break;
			}
		}
	}

	void DeviceOpenGL::CreateBuffer(BufferHandle hBuffer)
	{
		BufferInternal&	newBuffer = hBuffer->m_internal;

		glGenBuffersARB(1, &newBuffer.m_bufferID);
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, newBuffer.m_bufferID);
		glBufferDataARB(GL_ARRAY_BUFFER_ARB, hBuffer->m_numElements * hBuffer->m_stride, hBuffer->m_pInitData, GL_STATIC_DRAW_ARB);
	}

	void DeviceOpenGL::CreateRenderTarget(RenderTargetHandle hRenderTarget)
	{
		RenderTargetInternal& renderTarget = hRenderTarget->m_internal;

		// Look for something valid
		bool systemTarget = !hRenderTarget->m_hDepthSurface.IsValid();
		cr::size_t i = 0;
		while (systemTarget && i < Constants::MaxColourTargets)
		{
			systemTarget = !hRenderTarget->m_hColourSurface[i].IsValid();
			++i;
		}

		if (systemTarget)
		{
			renderTarget.m_frameBufferObjectId = 0;
		}
		else
		{
			CR_ASSERT(glGetError() == GL_NO_ERROR);
			glGenFramebuffersEXT(1, &renderTarget.m_frameBufferObjectId);
			CR_ASSERT(glGetError() == GL_NO_ERROR);
			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, renderTarget.m_frameBufferObjectId);
			CR_ASSERT(glGetError() == GL_NO_ERROR);

			// Bind depth buffer
			if (hRenderTarget->m_hDepthSurface.IsValid())
			{
				GLuint bufferId = hRenderTarget->m_hDepthSurface->m_internal.m_bufferId;
				if (hRenderTarget->m_hDepthSurface->m_internal.m_isRenderBuffer)
				{
					glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, bufferId);
				}
				else
				{
					glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, bufferId, 0);
				}
				CR_ASSERT(glGetError() == GL_NO_ERROR);
			}

			// Bind colour buffers
			for (cr::uint32_t i = 0; i < Constants::MaxColourTargets; ++i)
			{
				if (hRenderTarget->m_hColourSurface[i].IsValid())
				{
					GLuint bufferId = hRenderTarget->m_hColourSurface[i]->m_internal.m_bufferId;

					if (hRenderTarget->m_hColourSurface[i]->m_internal.m_isRenderBuffer)
					{
						glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT+i, GL_RENDERBUFFER_EXT, bufferId);
					}
					else
					{
						glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT+i, GL_TEXTURE_2D, bufferId, 0);
					}
					
					CR_ASSERT(glGetError() == GL_NO_ERROR);
				}
			}

			CR_ASSERT(GL_FRAMEBUFFER_COMPLETE_EXT == glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT));

			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		}
	}

	void DeviceOpenGL::CreateRenderSurface(RenderSurfaceHandle hRenderSurface)
	{
		RenderSurfaceInternal& renderSurface = hRenderSurface->m_internal;

		GLenum componentInternalFormat;
		GLenum componentFormat;
		GLenum componentType;
		TypeConversion::ConvertBufferFormat(hRenderSurface->m_format, &componentInternalFormat, &componentFormat, &componentType);
		CR_ASSERT(componentInternalFormat != GL_INVALID_ENUM);

		if (hRenderSurface->m_createTexture)
		{
			// If this is a colour target, create a normal texture
			if (BufferFormat::IsColourFormat(hRenderSurface->m_format))
			{
				hRenderSurface->m_surfaceAsTexture.m_width = hRenderSurface->m_width;
				hRenderSurface->m_surfaceAsTexture.m_height = hRenderSurface->m_height;
				hRenderSurface->m_surfaceAsTexture.m_format = hRenderSurface->m_format;
				CreateTexture(hRenderSurface->m_surfaceAsTexture);
				renderSurface.m_bufferId = hRenderSurface->m_surfaceAsTexture.m_internal.m_textureId;
				renderSurface.m_isRenderBuffer = false;
			}
			else if (BufferFormat::IsDepthFormat(hRenderSurface->m_format))
			{
				// Depth buffer as texture...
				hRenderSurface->m_surfaceAsTexture.m_width = hRenderSurface->m_width;
				hRenderSurface->m_surfaceAsTexture.m_height = hRenderSurface->m_height;
				hRenderSurface->m_surfaceAsTexture.m_format = hRenderSurface->m_format;
				CreateTexture(hRenderSurface->m_surfaceAsTexture);
				renderSurface.m_bufferId = hRenderSurface->m_surfaceAsTexture.m_internal.m_textureId;
				renderSurface.m_isRenderBuffer = false;
			}
		}
		else
		{
			// Use optimised render buffers for "write only" targets
			glGenRenderbuffersEXT(1, &renderSurface.m_bufferId);
			glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderSurface.m_bufferId);
			glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, componentInternalFormat, hRenderSurface->m_width, hRenderSurface->m_height);
			renderSurface.m_isRenderBuffer = true;
		}

		CR_ASSERT(glGetError() == GL_NO_ERROR);
	}

	void DeviceOpenGL::CreateTexture(TextureHandle hTexture)
	{
		TextureInternal& newTexture = hTexture->m_internal;

		GLenum componentInternalFormat;
		GLenum componentFormat;
		GLenum componentType;
		TypeConversion::ConvertBufferFormat(hTexture->m_format, &componentInternalFormat, &componentFormat, &componentType);
		CR_ASSERT(componentInternalFormat != GL_INVALID_ENUM);

		glGenTextures(1, &newTexture.m_textureId);
		glBindTexture(GL_TEXTURE_2D, newTexture.m_textureId);
		glTexImage2D(GL_TEXTURE_2D, 0, componentInternalFormat, hTexture->m_width, hTexture->m_height, 0, componentFormat, componentType, hTexture->m_pInitData);

		GLenum err = glGetError();
		CR_ASSERT_MSG1(err == GL_NO_ERROR, "Error creation OpenGL texture: 0x%X", err);
	}

	void DeviceOpenGL::CreateStreamDescription(StreamDescriptionHandle /*hStreamDescription*/)
	{
		
	}

	void DeviceOpenGL::CreateRenderBinding(RenderBindingHandle hRenderBinding)
	{

	}

	void DeviceOpenGL::CreateGpuProgram(GpuProgramHandle hGpuProgram)
	{

	}

	void DeviceOpenGL::CreateStateBlock(StateBlockHandle hStateBlock)
	{

	}

	void DeviceOpenGL::CreateConstantBuffer(ConstantBufferHandle hConstantBuffer)
	{

	}


	// Object destruction
	void DeviceOpenGL::ReleaseContext(ContextHandle hContext)
	{
		switch (hContext->m_type)
		{
		case ContextType::Immediate:
			{
				// Nothing to do
				break;
			}
		case ContextType::Deferred:
			{
				// Clean up this context if necessary
				break;
			}
		}

		cr::ClearObject(hContext->m_internal);
		CR_ASSERT(glGetError() == GL_NO_ERROR);
	}

	void DeviceOpenGL::ReleaseBuffer(BufferHandle hBuffer)
	{
		glDeleteBuffersARB(1, &hBuffer->m_internal.m_bufferID);
		cr::ClearObject(hBuffer->m_internal);
		CR_ASSERT(glGetError() == GL_NO_ERROR);
	}

	void DeviceOpenGL::ReleaseRenderTarget(RenderTargetHandle hRenderTarget)
	{
		glDeleteFramebuffersEXT(1, &hRenderTarget->m_internal.m_frameBufferObjectId);
		cr::ClearObject(hRenderTarget->m_internal);
		CR_ASSERT(glGetError() == GL_NO_ERROR);
	}

	void DeviceOpenGL::ReleaseRenderSurface(RenderSurfaceHandle hRenderSurface)
	{
		if (hRenderSurface->m_internal.m_isRenderBuffer)
		{
			VALIDATE_GL(glDeleteRenderbuffersEXT(1, &hRenderSurface->m_internal.m_bufferId));
		}
		else
		{
			ReleaseTexture(hRenderSurface->m_surfaceAsTexture);
		}

		cr::ClearObject(hRenderSurface->m_internal);
		CR_ASSERT(glGetError() == GL_NO_ERROR);
	}

	void DeviceOpenGL::ReleaseTexture(TextureHandle hTexture)
	{
		glDeleteTextures(1, &hTexture->m_internal.m_textureId);
		cr::ClearObject(hTexture->m_internal);
		CR_ASSERT(glGetError() == GL_NO_ERROR);
	}

	void DeviceOpenGL::ReleaseStreamDescription(StreamDescriptionHandle /*hStreamDescription*/)
	{
		
	}

	void DeviceOpenGL::ReleaseRenderBinding(RenderBindingHandle hRenderBinding)
	{

	}

	void DeviceOpenGL::ReleaseGpuProgram(GpuProgramHandle hGpuProgram)
	{

	}

	void DeviceOpenGL::ReleaseStateBlock(StateBlockHandle hStateBlock)
	{

	}

	void DeviceOpenGL::ReleaseConstantBuffer(ConstantBufferHandle hConstantBuffer)
	{

	}
}

#endif // GK_DEVICE_OPENGL